using System;
using System.IO;
using System.Xml;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;


namespace CS6238Project2.Banking.Tools
{
    /// <summary>
    /// Cryptography Helper
    /// </summary>
    public static class Crypto
    {
        #region Public Methods

        [DebuggerStepThrough()]
        public static byte[] GenerateRandomKey()
        {
            using(SymmetricAlgorithm algorithm = Rijndael.Create())
            {
                algorithm.IV = new byte[algorithm.IV.Length];

                return algorithm.Key;
            }
        }

        [DebuggerStepThrough()]
        public static byte[] Encrypt(byte[] key,
        byte[] plainBytes)
        {
            using(SymmetricAlgorithm crypto = CreateAlgorithm(key))
            {
                return Read(crypto.CreateEncryptor(), plainBytes);
            }
        }

        [DebuggerStepThrough()]
        public static byte[] Decrypt(byte[] key,
        byte[] cipherBytes)
        {
            using(SymmetricAlgorithm crypto = CreateAlgorithm(key))
            {
                return Read(crypto.CreateDecryptor(), cipherBytes);
            }
        }

        [DebuggerStepThrough()]
        public static RSACryptoServiceProvider CreateRSA()
        {
            CspParameters parms = new CspParameters();

            //Needs to use the MachineKeyStore otherwise ASP.net application 
            //wont work
            parms.Flags = CspProviderFlags.UseMachineKeyStore;

            return new RSACryptoServiceProvider(parms);
        }

        [DebuggerStepThrough()]
        public static RSACryptoServiceProvider CreateRSA(string key)
        {
            RSACryptoServiceProvider csp = CreateRSA();

            csp.FromXmlString(key);

            return csp;
        }

        [DebuggerStepThrough()]
        public static string Sign(string privateKey,
        string cipherData)
        {
            using(RSACryptoServiceProvider rsa = CreateRSA(privateKey))
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(cipherData);

                SignedXml signDoc = new SignedXml(doc);
                signDoc.SigningKey = rsa;
                signDoc.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl;

                Reference reference = new Reference(string.Empty);
                reference.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));
                signDoc.AddReference(reference);
                signDoc.ComputeSignature();

                XmlElement xelSign = signDoc.GetXml();

                doc.DocumentElement.AppendChild(xelSign);

                return doc.OuterXml;
            }
        }

        [DebuggerStepThrough()]
        public static bool IsTampered(string publicKey,
        string signedData)
        {
            using(RSACryptoServiceProvider csp = CreateRSA(publicKey))
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(signedData);

                SignedXml signDoc = new SignedXml(doc);

                XmlElement xelSignature = doc.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0] as XmlElement;

                signDoc.LoadXml(xelSignature);

                return !signDoc.CheckSignature(csp);
            }
        }

        #endregion

        #region Helper Methods

        [DebuggerStepThrough()]
        private static SymmetricAlgorithm CreateAlgorithm(byte[] key)
        {
            SymmetricAlgorithm algorithm = Rijndael.Create();

            algorithm.Key = key;
            algorithm.IV = new byte[algorithm.IV.Length];

            return algorithm;
        }

        [DebuggerStepThrough()]
        private static byte[] Read(ICryptoTransform transformer,
        byte[] data)
        {
            using(MemoryStream ms = new MemoryStream())
            {
                using (CryptoStream cs = new CryptoStream(ms, transformer, CryptoStreamMode.Write))
                {
                    cs.Write(data, 0, data.Length);
                    cs.FlushFinalBlock();

                    return ms.ToArray();
                }
            }
        }

        #endregion
	}
}